// Copyright 2008 Steve Spiller. All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without 
// modification, are permitted provided that the following conditions are met:
// 
//    1. Redistributions of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//    2. Redistributions in binary form must reproduce the above copyright 
//       notice, this list of conditions and the following disclaimer in the 
//       documentation and/or other materials provided with the distribution.
//       
// THIS SOFTWARE IS PROVIDED BY THE KAOS PROJECT ``AS IS'' AND ANY EXPRESS 
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 
// NO EVENT SHALL THE PROJECT OWNERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#pragma once

#include "SystemTypes.h"
#include <string>
#include <map>
#include <stack>
#include <vector>
#include <iostream>
#include <fstream>
#include <sstream>
#include <iomanip>

//
// Useful macros to identify a character
//
#define IS_LETTER(x)     (((x) >= 'a' && (x) <= 'z') || ((x) >= 'A' && (x) <= 'Z'))
#define IS_DIGIT(x)      ((x) >= '0' && (x) <= '9')
#define IS_HEXDIGIT(x)   (((x) >= '0' && (x) <= '9') || ((x) >= 'a' && (x) <= 'f') || ((x) >= 'A' && (x) <= 'F'))
#define IS_WHITESPACE(x) ((x) == ' ' || (x) == '\t' || (x) == '\r' || (x) == '\n')

//
// Forward class declarations
//
class CLpcToken;
class CLpcFile;

//
// Exception class that provides information about scanner errors
//
class ScannerException: public std::exception
{
public:
    ScannerException(const char * msg, int line = -1, int pos = -1) 
        : m_msg(msg),
          m_line(line),
          m_pos(pos)
    { 
    }
    
    virtual const char* what() const throw()
    {
        std::ostringstream msg;

        msg << m_msg;

        if(m_line > -1)
        {
            msg << ", line: " << m_line;
        }

        if(m_pos > -1)
        {
            msg << ", position: " << m_pos;
        }

        return msg.str().c_str();
    }
    
private:
    const char * m_msg;
    int          m_line;
    int          m_pos;
};

//
// The main class for tokenizing LPC files
//
class CLpcScanner
{
public:
    //
    // LPC Language Tokens
    //
    enum LpcToken
    {
        None,
        
        //
        // General
        //
        Identifier, Literal,

        //
        // Keywords
        //
        Array, Break, Buffer, Case, Catch, Class, Continue, Default, Do,
        Efun, Else, Float, For, ForEach, Function, If, In, Inherit, Int,
        Mapping, Mixed, New, NoMask, NoSave, Object, Private, Protected,
        Public, Ref, Return, Static, String, Switch, VarArgs, Void, While,

        //
        // Logical Operators
        //
        AndAnd, OrOr, EqualEqual, NotEqual, LessThan, GreaterThan,

        //
        // Operators
        //
        RightShift, LeftShift, PlusPlus, MinusMinus, Arrow, Scope,

        //
        // Assignment
        //
        RightShiftEqual, LeftShiftEqual, PlusEqual, MinusEqual, TimesEqual,
        DivideEqual, ModulusEqual, AndEqual, CaratEqual, OrEqual, TildeEqual,
        GreaterThanEqual, LessThanEqual,

        //
        // Symbols
        //
        OpenMapping, CloseMapping, OpenFunction, CloseFunction, OpenArray,
        CloseArray, Ellipsis, DotDot, Carat, Pipe, Question, Dollar,
        SemiColon, OpenBrace, CloseBrace, Comma, Colon, Assign,
        OpenParen, CloseParen, OpenBracket, CloseBracket, Period, 
        Ampersand, Bang, Tilde, Minus, Plus, Star, Slash, Percent,

        //
        // Special 
        //
        EndOfFile
    };

    //
    // LPC Literal Types
    //
    enum LpcLiteralType
    {
        NotALiteral,
        IntegerLiteral,
        RealLiteral,
        StringLiteral
    };

    static const char *tokenNames[]; // Holds the token names as strings
    
public:
    CLpcScanner(std::string file);
    ~CLpcScanner();

    CLpcToken *GetToken();
    void SetOutputStream(std::ostream & stream);

private:
    void PopulateReservedWords();
    LpcToken GetReservedWordToken(std::string word);
    
    CLpcToken *ProcessIdentifier();
    CLpcToken *ProcessString();
    CLpcToken *ProcessCharacter();
    CLpcToken *ProcessNumber();
    CLpcToken *ProcessSpecial();

    void HandleDirective();
    void HandleInclude();
    void HandleDefine();
    void HandleIf();
    void HandleUndef();
    void HandleIfdef();
    void HandleIfndef();
    void HandleError();
    void HandleWarn();
    void HandlePragma();

    inline CLpcFile *File() { return m_fileStack.top(); }

private:
    std::string                                     m_fileName;
    std::map<std::string, CLpcScanner::LpcToken>    m_reservedWords;
    std::stack<CLpcFile *>                          m_fileStack;
    std::ostream *                                  m_outputStream;
    INT                                             m_nestLevel;
    bool                                            m_inArray;
};

//
// Class type returned from the scanner that holds information about
// the token parsed.
//
class CLpcToken
{
public:
    CLpcToken(CLpcScanner::LpcToken token, std::string meme)
        : m_token(token),
          m_type(CLpcScanner::NotALiteral),
          m_meme(meme)
    {
        m_literal.i = 0;
    }

    CLpcToken(CLpcScanner::LpcToken token, std::string meme, CLpcScanner::LpcLiteralType type, INT i)
        : m_token(token),
          m_type(type),
          m_meme(meme)
    {
        m_literal.i = i;
    }

    CLpcToken(CLpcScanner::LpcToken token, std::string meme, CLpcScanner::LpcLiteralType type, FLOAT f)
        : m_token(token),
          m_type(type),
          m_meme(meme)
    {
        m_literal.f = f;
    }

    CLpcToken(CLpcScanner::LpcToken token, std::string meme, CLpcScanner::LpcLiteralType type, CHAR *str)
        : m_token(token),
          m_type(type),
          m_meme(meme)
    {
        m_literal.str = str;
    }

    inline CLpcScanner::LpcToken        GetToken()          { return m_token; }
    inline CLpcScanner::LpcLiteralType  GetLiteralType()    { return m_type; }
    inline std::string                  GetMeme()           { return m_meme; }
    inline INT                          GetIntLiteral()     { return m_literal.i; }
    inline FLOAT                        GetRealLiteral()    { return m_literal.f; }
    inline std::string                  GetStringLiteral()  { return std::string(m_literal.str); }

private:
    union
    {
        INT         i;
        FLOAT       f;
        CHAR *      str;
    }                               m_literal;
    CLpcScanner::LpcToken           m_token;
    CLpcScanner::LpcLiteralType     m_type;
    std::string                     m_meme;
};

//
// Class that holds information on a file being scanned
//
class CLpcFile
{
public:
    CLpcFile(std::string fileName);
    ~CLpcFile();

    void Load();

    CHAR ReadNext();
    CHAR PeekNext();
    
    bool EoF()          { return m_eof; }
    INT  LineNumber()   { return m_lineNumber; }
    CHAR Current()      { return m_currentChar; }
    INT  Position()     { return m_currentIndex; }

    void SetOutputStream(std::ostream & stream);

    void SetInString(bool inStr) { m_inString = inStr; }

private:
    std::string                                     m_fileName;
    std::vector<std::string>::iterator              m_lineIterator;
    std::vector<std::string>                        m_lines;
    std::string                                     m_currentLine;
    std::ostream *                                  m_outputStream;
    CHAR                                            m_currentChar;
    size_t                                          m_currentIndex;
    INT                                             m_lineNumber;
    bool                                            m_eof;
    bool                                            m_inString;
};
